5. Compilación de Resultados y Validación
5.1. Cargar las bibliotecas requeridas
El siguiente código carga las bibliotecas requeridas.
[1]:
import numpy as np # ndarrays for gridded data
import pandas as pd # DataFrames for tabular data
import geostatspy.GSLIB as GSLIB # GSLIB utilities, visualization and wrapper
import geostatspy.geostats as geostats # GSLIB methods convert to Python
import geostatspy
print('GeostatsPy version: ' + str(geostatspy.__version__))
import os # set working directory, run executables
from tqdm import tqdm # suppress the status bar
from functools import partialmethod
tqdm.__init__ = partialmethod(tqdm.__init__, disable=True)
ignore_warnings = True # ignore warnings?
import matplotlib.pyplot as plt # for plotting
import matplotlib as mpl # custom colorbar
import matplotlib.ticker as mticker # custom colorpar ticks
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator) # control of axes ticks
plt.rc('axes', axisbelow=True) # plot all grids below the plot elements
if ignore_warnings == True:
import warnings
warnings.filterwarnings('ignore')
import os
import matplotlib.image as mpimg
import subprocess
import time
from matplotlib.offsetbox import AnchoredText
import matplotlib.gridspec as gridspec
import plotly.io as pio
import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as py
# trimmed inverted rainbow colormap hsv removing magenta tones
cmap = mpl.colors.ListedColormap(mpl.cm.hsv(np.linspace(0.0, 0.70, 256))).reversed()
GeostatsPy version: 0.0.78
[2]:
def GSLIB2ndarray_3D(data_file, kcol,nreal, nx, ny, nz):
if nz > 1 and ny > 1:
array = np.ndarray(shape = (nreal, nz, ny, nx), dtype=object, order="F")
elif ny > 1:
array = np.ndarray(shape=(nreal, ny, nx), dtype=object, order="F")
else:
array = np.ndarray(nreal, nx, dtype=object)
with open(data_file) as f:
head = [next(f) for _ in range(2)] # read first two lines
line2 = head[1].split()
ncol = int(line2[0]) # get the number of columns
for icol in range(ncol): # read over the column names
head = next(f)
if icol == kcol:
col_name = head.split()[0]
for ineal in range(nreal):
if nz > 1 and ny > 1:
for iz in range(nz):
for iy in range(ny):
for ix in range(nx):
head = next(f)
array[ineal][nz - 1 - iz][ny - 1 - iy][ix] = head.split()[kcol]
elif ny > 1:
for iy in range(ny):
for ix in range(0, nx):
head = next(f)
array[ineal][ny - 1 - iy][ix] = head.split()[kcol]
else:
for ix in range(nx):
head = next(f)
array[ineal][ix] = head.split()[kcol]
return array, col_name
Nuestro punto de partida sera la base de datos compositada a 6 metros y simplificada pero completa, es decir, sin filtrar aquellas muestras por UG.
[3]:
## Cargar data simplificada de la sección anterior
import pandas as pd
import numpy as np
# Cargar base de datos en pandas y aproximar a dos decimales
df = pd.read_csv('data.csv', sep=',', encoding='latin1').round(2)
df['UG'] = df['UG'].astype(int) # Asegurarse de que la columna 'UG' sea de tipo entero
df # mostrar la data
[3]:
| X | Y | Z | cu_pct | UG | |
|---|---|---|---|---|---|
| 0 | 472187.34 | 6925805.69 | 4211.77 | 0.02 | 2 |
| 1 | 472187.74 | 6925806.53 | 4205.84 | 0.01 | 2 |
| 2 | 472188.09 | 6925807.38 | 4199.92 | 0.01 | 2 |
| 3 | 472188.47 | 6925808.28 | 4194.00 | 0.01 | 2 |
| 4 | 472188.88 | 6925809.22 | 4188.08 | 0.01 | 2 |
| ... | ... | ... | ... | ... | ... |
| 16877 | 471922.69 | 6925391.61 | 4022.52 | 0.09 | 4 |
| 16878 | 471924.63 | 6925394.53 | 4017.65 | 0.11 | 4 |
| 16879 | 471926.59 | 6925397.45 | 4012.78 | 0.18 | 4 |
| 16880 | 471928.55 | 6925400.36 | 4007.92 | 0.15 | 4 |
| 16881 | 471930.53 | 6925403.26 | 4003.05 | 0.21 | 4 |
16882 rows × 5 columns
[4]:
# Estadísticas por UG (Number, Max, Min, Mean, Variance)
# Asegúrese de haber ejecutado previamente la celda que carga 'df' (data.csv)
import pandas as pd
# lista de UGs a reportar (ajustar si hay más o diferentes!!!)
ug_list = [2, 3, 4]
stats = {}
for ug in ug_list:
serie = df.loc[df['UG'] == ug, 'cu_pct']
stats[f'UG {ug}'] = serie.agg(['count', 'max', 'min', 'mean', 'var'])
# estadisticas globales (todas las muestras)
global_stats = df['cu_pct'].agg(['count', 'max', 'min', 'mean', 'var'])
# montar DataFrame final con el orden solicitado - AJUSTAR SI HAY MÁS UGS!!!
df_stats = pd.DataFrame({
'UG 2': stats['UG 2'],
'UG 3': stats['UG 3'],
'UG 4': stats['UG 4'],
'Global': global_stats
})
# Renombrar índices a los labels solicitados
df_stats.index = ['Número', 'Máximo', 'Mínimo', 'Media', 'Varianza']
# Mostrar con 2 decimales (ajustar si lo desea)
from IPython.display import display
display(df_stats.round(2))
# Opcional: exportar a csv para revisión fuera del notebook y ajustar decimales
# df_stats.round(6).to_csv('./03_resultados/summary_stats_UGs.csv')
| UG 2 | UG 3 | UG 4 | Global | |
|---|---|---|---|---|
| Número | 6203.00 | 2048.00 | 8631.00 | 16882.00 |
| Máximo | 2.03 | 1.64 | 6.72 | 6.72 |
| Mínimo | 0.00 | 0.00 | 0.00 | 0.00 |
| Media | 0.06 | 0.13 | 0.25 | 0.17 |
| Varianza | 0.01 | 0.02 | 0.05 | 0.04 |
5.2. Cargar modelos de UGs y Leyes
El siguiente código carga nuestras estimaciones previas, tanto de UGs como de leyes.
[5]:
xmin = 471116.24; xmax = 473023.89 # rango de valores x
ymin = 6924721.57; ymax = 6926164.63 # rango de valores y
zmin = 2950.02; zmax = 4445.78 # rango de valores z
xsiz = 15; ysiz = 15; zsiz = 15 # tamaño de celda
nx = 128; ny = 97; nz = 100 # número de celdas
tmin = -999; tmax = 999; # límites de recorte de datos
# calculo automático del centro de las capas y cortes para plotear, no modificar
icx = int(nx/2); cx = xmin + icx*xsiz
icy = int(ny/2); cy = ymin + icy*ysiz
icz = int(nz/2); cz = zmin + icz*zsiz
[6]:
# Cargar modelos de UGs
modelo_ug = GSLIB2ndarray_3D("./03_resultados/modelo_ug.dat ", 0, 1, nx, ny, nz)[0][0].astype(int)
# Cargar modelos de leyes
estCU_UG2 = GSLIB2ndarray_3D("./03_resultados/modelo_leyes_UG2.dat ", 0, 1, nx, ny, nz)[0][0].astype(float)
categoria_UG2 = GSLIB2ndarray_3D("./03_resultados/modelo_categorias_UG2.dat ", 0, 1, nx, ny, nz)[0][0]
estCU_UG3 = GSLIB2ndarray_3D("./03_resultados/modelo_leyes_UG3.dat ", 0, 1, nx, ny, nz)[0][0].astype(float)
categoria_UG3 = GSLIB2ndarray_3D("./03_resultados/modelo_categorias_UG3.dat ", 0, 1, nx, ny, nz)[0][0]
estCU_UG4 = GSLIB2ndarray_3D("./03_resultados/modelo_leyes_UG4.dat ", 0, 1, nx, ny, nz)[0][0].astype(float)
categoria_UG4 = GSLIB2ndarray_3D("./03_resultados/modelo_categorias_UG4.dat ", 0, 1, nx, ny, nz)[0][0]
[7]:
## Combinar modelos de leyes en un solo modelo final de acuerdo a las UGs
modelo_leyes_final = np.full((nz, ny, nx), np.nan) # inicializar con NaN
for iz in range(nz):
for iy in range(ny):
for ix in range(nx):
ug_value = modelo_ug[iz, iy, ix]
if ug_value == 2:
modelo_leyes_final[iz, iy, ix] = estCU_UG2[iz, iy, ix]
elif ug_value == 3:
modelo_leyes_final[iz, iy, ix] = estCU_UG3[iz, iy, ix]
elif ug_value == 4:
modelo_leyes_final[iz, iy, ix] = estCU_UG4[iz, iy, ix]
## Combinar modelos de categorías en un solo modelo final de acuerdo a las UGs
modelo_categorias_final = np.full((nz, ny, nx), "Sin_Categoria", dtype=object) # inicializar con "Sin_Categoria"
for iz in range(nz):
for iy in range(ny):
for ix in range(nx):
ug_value = modelo_ug[iz, iy, ix]
if ug_value == 2:
modelo_categorias_final[iz, iy, ix] = categoria_UG2[iz, iy, ix]
elif ug_value == 3:
modelo_categorias_final[iz, iy, ix] = categoria_UG3[iz, iy, ix]
elif ug_value == 4:
modelo_categorias_final[iz, iy, ix] = categoria_UG4[iz, iy, ix]
[8]:
continuous = 'cu_pct'
x_coords = np.linspace(xmin + xsiz/2, xmax - xsiz/2, nx)
y_coords = np.linspace(ymin + ysiz/2, ymax - ysiz/2, ny)
z_coords = np.linspace(zmin + zsiz/2, zmax - zsiz/2, nz)
import plotly.io as pio
# pio.renderers.default = "colab"
pio.renderers.default = "notebook" # usar esta linea en jupyter notebook o vscode
import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as py
fig = go.Figure(data=[go.Scatter3d(
x=df['X'],
y=df['Y'],
z=df['Z'],
marker=dict(color=df[continuous],
colorscale=px.colors.sequential.Rainbow[1:],
cmin=0.0,
cmax=df[continuous].quantile(0.95),
size=2.0, # Esta opcion controla el tamaño de los datos
colorbar=dict(
title='Cu [%]',
thickness=20,
# Add a border to the colorbar
outlinecolor='black', # Color of the border
outlinewidth=2, # Width of the border
bordercolor='white', # Background border color (if applicable)
borderwidth=1 # Background border width
)
),
mode='markers',
opacity=1
)])
# otras opciones para controlar el color de los elementos de la figura, borrar hovermode para desactivar etiquetas al pasar el mouse
fig.update_layout( title_text='Estimación de Cu - UG 4',
scene = dict(
xaxis = dict(
title='Este',
backgroundcolor="white",
gridcolor="gray",
showbackground=True,
zerolinecolor="white",),
yaxis = dict(
title='Norte',
backgroundcolor="white",
gridcolor="gray",
showbackground=True,
zerolinecolor="white"),
zaxis = dict(
title='Elevación',
backgroundcolor="white",
gridcolor="gray",
showbackground=True,
zerolinecolor="white",),
),
font_family="Times New Roman",
font_color="black", hovermode=False
)
# ocultar la leyenda/autonombre "trace 0" junto al colorbar
fig.update_layout(showlegend=False)
# definir cortes en x, y, z
icz = 60 # índice de corte en z (puede ajustarse), va entre 0 y nz-1
# Agregar cortes del modelo de UGs con la misma escala de colores de plotly
x_slice = np.flip(np.flipud(modelo_leyes_final[:, :, icx]), axis=1)
y_slice = np.flip(modelo_leyes_final[:, icy, :], axis=0)
z_slice = np.flip(modelo_leyes_final[nz-icz, :, :], axis=0)
# X-slice (plano constante x)
YY, ZZ = np.meshgrid(y_coords, z_coords) # shapes (nz, ny)
XX = np.full_like(YY, x_coords[icx])
surf_x = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=x_slice.astype(float),
colorscale=px.colors.sequential.Rainbow[1:],
cmin=0.0,
cmax=df[continuous].quantile(0.95),
showscale=False,
opacity=1,
name=f'corte X={x_coords[icx]:.1f}',
hoverinfo='skip',
hovertemplate=None
)
# Y-slice (plano constante y)
XX, ZZ = np.meshgrid(x_coords, z_coords) # shapes (nz, nx)
YY = np.full_like(XX, y_coords[icy])
surf_y = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=y_slice.astype(float),
colorscale=px.colors.sequential.Rainbow[1:],
cmin=0.0,
cmax=df[continuous].quantile(0.95),
showscale=False,
opacity=1,
name=f'corte Y={y_coords[icy]:.1f}',
hoverinfo='skip',
hovertemplate=None,
)
# Z-slice (plano constante z)
XX, YY = np.meshgrid(x_coords, y_coords) # shapes (ny, nx)
ZZ = np.full_like(XX, z_coords[icz])
surf_z = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=z_slice.astype(float),
colorscale=px.colors.sequential.Rainbow[1:],
cmin=0.0,
cmax=df[continuous].quantile(0.95),
showscale=False,
opacity=1,
name=f'corte Z={z_coords[icz]:.1f}',
hoverinfo='skip',
hovertemplate=None
)
# Añadir superficies al figure (manteniendo la leyenda y colores consistentes)
fig.add_trace(surf_x)
fig.add_trace(surf_y)
fig.add_trace(surf_z)
fig.show()
[9]:
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
ROWS = 3
COLS = 2
# Crear listado de bancos
step_size = 15
max_benches = ROWS*COLS
lims = np.arange(4250,4250-step_size*max_benches,-step_size)
continuous = 'cu_pct'
# Initialize figure with subplots
# Initialize figure with subplots
fig = make_subplots(
rows=ROWS, cols=COLS, horizontal_spacing=0.2, vertical_spacing=0.1,
subplot_titles=[f"Cu [%], banco {lims[i]+step_size/2}" for i in range(len(lims))]
)
for i, lim in enumerate(lims):
data_temp = df.loc[(df['Z'] > lim) & (df['Z'] <= lim + step_size)]
# Calculate row and column indices for the subplot (1-based indexing)
row = i // COLS + 1
col = i % COLS + 1
# agregar Z-slice del modelo_leyes_final correspondiente al banco (plano constante z)
z_mid = lim + step_size/2.0
iz = int(np.abs(z_coords - z_mid).argmin()) # índice de la capa más cercana al centro del banco
# extraer y orientar la rebanada (debe quedar shape (ny, nx) con y_coords como filas)
z_slice2d = np.flip(modelo_leyes_final[nz-iz, :, :].astype(float), axis=0)
# enmascarar valores inválidos (-999) para que sean transparentes en el heatmap
z_slice2d[z_slice2d <= -998] = np.nan
heat = go.Heatmap(
x=x_coords,
y=y_coords,
z=z_slice2d,
colorscale=px.colors.sequential.Rainbow[1:],
zmin=0.0,
zmax=df[continuous].quantile(0.95),
showscale=False,
opacity=0.7,
hoverinfo='skip',
name=f'Z-slice {z_coords[iz]:.1f}'
)
# añadir primero el heatmap (fondo) y luego el scatter (muestras) conserva la visibilidad
fig.add_trace(heat, row=row, col=col)
fig.add_trace(
go.Scatter(
x=data_temp['X'],
y=data_temp['Y'],
marker=dict(
color=data_temp[continuous],
colorscale=px.colors.sequential.Rainbow[1:],
cmin=0.0,
cmax=df[continuous].quantile(0.95),
size=5.0,
line=dict(
color='black',
width=0.5
),
),
mode='markers',
opacity=1,
showlegend=False
),
row=row, col=col
)
# Update xaxis properties
fig.update_xaxes(title_text="Este", range=[df['X'].quantile(0.015), df['X'].quantile(0.97)], gridcolor='rgba(0,0,0,0.2)', row=row, col=col)
# Update yaxis properties
fig.update_yaxes(title_text="Norte", range=[df['Y'].quantile(0.03), df['Y'].quantile(0.99)], gridcolor='rgba(0,0,0,0.2)', row=row, col=col)
fig.update_annotations(font_size=12)
# otras opciones para controlar el color de los elementos de la figura
fig.update_layout(font_family="Times New Roman",font_size=8,
font_color="black", plot_bgcolor='white', autosize = False,
width = 600,
height = 900, hovermode=False)
fig.show()
[25]:
from plotly.subplots import make_subplots
## truncate Z variable in the df axis every x meters
df_swath = df.copy()
dz = 50 # truncar cada 50 metros
df_swath['Z'] = df_swath['Z'].where(df_swath['Z'] % dz == 0, other=df_swath['Z'] - df_swath['Z'] % dz)
## Plot truncated z values against filtered average cut > 0 percentage with a line plot and marginal counter histogram
import plotly.express as px
import plotly.graph_objects as go
# prepare aggregated line data
agg = df_swath[df_swath['cu_pct'] >= 0].groupby('Z').agg(
mean_cut=('cu_pct', 'mean'),
count=('cu_pct', 'count')
).reset_index()
# create subplot: top = line, bottom = marginal histogram (shared x)
# enable a secondary y-axis for the bottom subplot so we can plot the bar on the right
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
vertical_spacing=0.05,
row_heights=[0.7, 0.3],
specs=[[{"type": "xy"}],
[{"type": "xy", "secondary_y": True}]])
# line trace (top)
fig.add_trace(
go.Scatter(
x=agg['Z'],
y=agg['mean_cut'],
mode='lines+markers',
name='Ley Promedio Cu [%] en muestras',
line=dict(color='blue'),
marker=dict(size=6, color='blue')
),
row=1, col=1
)
## add z vs filtered line
# line trace (top)
fig.add_trace(
go.Scatter(
x=z_coords,
y=[np.mean(np.mean(modelo_leyes_final[nz-iz-1, :, :][modelo_leyes_final[nz-iz-1, :, :] > 0])) for iz in range(nz)],
mode='lines',
name='Ley Promedio Cu (%) en modelo',
line=dict(color='red')
),
row=1, col=1
)
# marginal histogram (bottom)
fig.add_trace(
go.Histogram(
x=df_swath[df_swath['cu_pct'] >= 0]['Z'],
nbinsx=30,
marker=dict(color='blue'),
opacity=0.3,
name='Conteo muestras'
),
row=2, col=1
)
# secondary axis: count of valid cells (>0) per model z-layer
valid_counts = [
int(np.sum((modelo_leyes_final[nz - iz - 1, :, :] > 0) & np.isfinite(modelo_leyes_final[nz - iz - 1, :, :])))
for iz in range(nz)
]
bar = go.Bar(
x=z_coords,
y=valid_counts,
name='N° celdas válidas (modelo)',
marker=dict(color='red'),
opacity=0.3
)
## add bar y-axis on the right, row=2, col=1.
# add the bar to the bottom subplot using the secondary y-axis
fig.add_trace(bar, row=2, col=1, secondary_y=True)
# Update 2nd y-axis properties for the bar chart
fig.update_yaxes(
title_text='Celdas válidas (modelo)',
secondary_y=True,
row=2, col=1
)
# layout and axis labels
fig.update_xaxes(title_text='Elevación (m)', row=2, col=1)
fig.update_yaxes(title_text='Ley Promedio Cu (%)', row=1, col=1)
fig.update_yaxes(title_text='Conteo muestras', row=2, col=1)
fig.update_yaxes(title_text='Celdas válidas (modelo)', row=2, col=1, secondary_y=True)
fig.update_layout(
title='Ley Promedio de Cu vs Elevación (m)',
font_family="Times New Roman",
font_size=12,
font_color="black",
plot_bgcolor='white',
hovermode=False,
showlegend=True,
bargap=0.1,
height=600,
width=900
)
fig.show()
[28]:
## esta vez para UG 4
from plotly.subplots import make_subplots
## truncate Z variable in the df axis every x meters
df_swath = df[df['UG'] == 4].copy()
dz = 50 # truncar cada 50 metros
df_swath['Z'] = df_swath['Z'].where(df_swath['Z'] % dz == 0, other=df_swath['Z'] - df_swath['Z'] % dz)
## Plot truncated z values against filtered average cut > 0 percentage with a line plot and marginal counter histogram
import plotly.express as px
import plotly.graph_objects as go
# prepare aggregated line data
agg = df_swath[df_swath['cu_pct'] >= 0].groupby('Z').agg(
mean_cut=('cu_pct', 'mean'),
count=('cu_pct', 'count')
).reset_index()
# create subplot: top = line, bottom = marginal histogram (shared x)
# enable a secondary y-axis for the bottom subplot so we can plot the bar on the right
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
vertical_spacing=0.05,
row_heights=[0.7, 0.3],
specs=[[{"type": "xy"}],
[{"type": "xy", "secondary_y": True}]])
# line trace (top)
fig.add_trace(
go.Scatter(
x=agg['Z'],
y=agg['mean_cut'],
mode='lines+markers',
name='Ley Promedio Cu [%] en muestras',
line=dict(color='blue'),
marker=dict(size=6, color='blue')
),
row=1, col=1
)
modelo_leyes_final_UG4 = np.full((nz, ny, nx), np.nan) # inicializar con NaN
for iz in range(nz):
for iy in range(ny):
for ix in range(nx):
if modelo_ug[iz, iy, ix] == 4:
modelo_leyes_final_UG4[iz, iy, ix] = estCU_UG4[iz, iy, ix]
## add z vs filtered line
# line trace (top)
fig.add_trace(
go.Scatter(
x=z_coords,
y=[np.mean(np.mean(modelo_leyes_final_UG4[nz-iz-1, :, :][modelo_leyes_final_UG4[nz-iz-1, :, :] > 0])) for iz in range(nz)],
mode='lines',
name='Ley Promedio Cu (%) en modelo',
line=dict(color='red')
),
row=1, col=1
)
# marginal histogram (bottom)
fig.add_trace(
go.Histogram(
x=df_swath[df_swath['cu_pct'] >= 0]['Z'],
nbinsx=30,
marker=dict(color='blue'),
opacity=0.3,
name='Conteo muestras'
),
row=2, col=1
)
# secondary axis: count of valid cells (>0) per model z-layer
valid_counts = [
int(np.sum((modelo_leyes_final_UG4[nz - iz - 1, :, :] > 0) & np.isfinite(modelo_leyes_final_UG4[nz - iz - 1, :, :])))
for iz in range(nz)
]
bar = go.Bar(
x=z_coords,
y=valid_counts,
name='N° celdas válidas (modelo)',
marker=dict(color='red'),
opacity=0.3
)
## add bar y-axis on the right, row=2, col=1.
# add the bar to the bottom subplot using the secondary y-axis
fig.add_trace(bar, row=2, col=1, secondary_y=True)
# Update 2nd y-axis properties for the bar chart
fig.update_yaxes(
title_text='Celdas válidas (modelo)',
secondary_y=True,
row=2, col=1
)
# layout and axis labels
fig.update_xaxes(title_text='Elevación (m)', row=2, col=1)
fig.update_yaxes(title_text='Ley Promedio Cu (%)', row=1, col=1)
fig.update_yaxes(title_text='Conteo muestras', row=2, col=1)
fig.update_yaxes(title_text='Celdas válidas (modelo)', row=2, col=1, secondary_y=True)
fig.update_layout(
title='Ley Promedio de Cu vs Elevación (m) - UG 4',
font_family="Times New Roman",
font_size=12,
font_color="black",
plot_bgcolor='white',
hovermode=False,
showlegend=True,
bargap=0.1,
height=600,
width=900
)
fig.show()
[82]:
from IPython.display import display
# entregar estadísticas de modelo_leyes_final según UG (Number, Max, Min, Mean, Variance)
ugs = [2, 3, 4]
results = {}
for ug in ugs:
mask = (modelo_ug == ug) & (modelo_leyes_final > 0)
vals = modelo_leyes_final[mask]
cnt = vals.size
if cnt > 0:
vals = vals.astype(float)
results[f'UG {ug}'] = {
'Número': int(cnt),
'Máximo': float(np.nanmax(vals)),
'Mínimo': float(np.nanmin(vals)),
'Media': float(np.nanmean(vals)),
'Varianza': float(np.nanvar(vals))
}
else:
results[f'UG {ug}'] = {'Número': 0, 'Máximo': np.nan, 'Mínimo': np.nan, 'Media': np.nan, 'Varianza': np.nan}
# Estadísticas globales (todas las celdas válidas)
mask_all = (modelo_leyes_final > 0) & np.isfinite(modelo_leyes_final)
vals_all = modelo_leyes_final[mask_all]
cnt_all = vals_all.size
if cnt_all > 0:
results['Global'] = {
'Número': int(cnt_all),
'Máximo': float(np.nanmax(vals_all)),
'Mínimo': float(np.nanmin(vals_all)),
'Media': float(np.nanmean(vals_all)),
'Varianza': float(np.nanvar(vals_all))
}
else:
results['Global'] = {'Número': 0, 'Máximo': np.nan, 'Mínimo': np.nan, 'Media': np.nan, 'Varianza': np.nan}
# Montar DataFrame y mostrar
df_stats_model = pd.DataFrame(results).T[['Número', 'Máximo', 'Mínimo', 'Media', 'Varianza']]
display(df_stats_model.round(2))
| Número | Máximo | Mínimo | Media | Varianza | |
|---|---|---|---|---|---|
| UG 2 | 73198.0 | 0.70 | 0.00 | 0.06 | 0.00 |
| UG 3 | 13488.0 | 0.74 | 0.01 | 0.13 | 0.01 |
| UG 4 | 124567.0 | 2.48 | 0.01 | 0.23 | 0.01 |
| Global | 211253.0 | 2.48 | 0.00 | 0.16 | 0.02 |
[56]:
# Ensure renderer is appropriate for your environment (notebook / vscode)
pio.renderers.default = 'notebook'
# --- Input assumptions (these must exist in the environment) ---
# df: pandas DataFrame with columns 'X','Y','Z'
# categoria_UG4: numpy array with shape (nz, ny, nx) containing strings: 'Inferido','Indicado','Medido'
# x_coords, y_coords, z_coords: 1D coordinate arrays for grid cell centers (lengths nx, ny, nz)
# icx, icy, icz: integer indices for the central slices to display
# nz, ny, nx: grid dimensions
# Map categories to numeric codes
cat_to_num = {'Inferido': 0, 'Indicado': 1, 'Medido': 2}
cat_num = np.vectorize(lambda x: cat_to_num.get(x, np.nan))(modelo_categorias_final)
cat_num = cat_num.astype(float) # allow NaNs
# Create slices (same convention used in the notebook)
x_slice = np.flip(np.flipud(cat_num[:, :, icx]), axis=1) # X-slice (nz, ny)
y_slice = np.flip(cat_num[:, icy, :], axis=0) # Y-slice (nz, nx)
z_slice = np.flip(cat_num[nz-icz, :, :], axis=0) # Z-slice (ny, nx)
# Discrete colors: Inferido (red), Indicado (yellow), Medido (green)
# We use normalized stops [0.0, 0.5, 1.0] so numeric codes 0,1,2 map exactly to the three colors
colorscale = [
[0.0, '#d73027'], # Inferido -> 0
[0.5, "#fff700"], # Indicado -> 1 (normalized to 0.5)
[1.0, '#1a9850'] # Medido -> 2 (normalized to 1.0)
]
# Build surface for X-slice
YY, ZZ = np.meshgrid(y_coords, z_coords) # shapes (nz, ny)
XX = np.full_like(YY, x_coords[icx])
surf_x = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=x_slice.astype(float),
colorscale=colorscale,
cmin=0, cmax=2,
showscale=False,
opacity=1,
name=f'corte X={x_coords[icx]:.1f}',
hoverinfo='skip',
hovertemplate=None
)
# Build surface for Y-slice
XX, ZZ = np.meshgrid(x_coords, z_coords) # shapes (nz, nx)
YY = np.full_like(XX, y_coords[icy])
surf_y = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=y_slice.astype(float),
colorscale=colorscale,
cmin=0, cmax=2,
showscale=False,
opacity=1,
name=f'corte Y={y_coords[icy]:.1f}',
hoverinfo='skip',
hovertemplate=None
)
# Build surface for Z-slice (we enable a colorbar here with categorical ticks)
XX, YY = np.meshgrid(x_coords, y_coords) # shapes (ny, nx)
ZZ = np.full_like(XX, z_coords[icz])
surf_z = go.Surface(
x=XX, y=YY, z=ZZ,
surfacecolor=z_slice.astype(float),
colorscale=colorscale,
cmin=0, cmax=2,
showscale=True,
colorbar=dict(
title='Categoría',
tickvals=[0, 1, 2],
ticktext=['Inferido', 'Indicado', 'Medido'],
thickness=20
),
opacity=1,
name=f'corte Z={z_coords[icz]:.1f}',
hoverinfo='skip',
hovertemplate=None
)
# Add sample points as neutral black markers for reference
scatter_samples = go.Scatter3d(
x=df['X'], y=df['Y'], z=df['Z'],
mode='markers',
marker=dict(color='black', size=2, opacity=0.8),
name='Muestras'
)
# Compose figure
fig = go.Figure(data=[scatter_samples, surf_x, surf_y, surf_z])
fig.update_layout(
title='Categoría (Inferido=rojo, Indicado=amarillo, Medido=verde)',
scene=dict(
xaxis=dict(title='Este'),
yaxis=dict(title='Norte'),
zaxis=dict(title='Elevación')
),
width=900,
height=720,
font=dict(family='Times New Roman', size=12)
)
fig.show()
[89]:
# Entregar para cada UG y cada categoría las estadísticas (Número, Max, Min, Mean, Variance) de modelo_leyes_final
from IPython.display import display
ugs = [2, 3, 4]
categories = ['Inferido', 'Indicado', 'Medido']
results = {}
for ug in ugs:
for cat in categories:
mask = (modelo_ug == ug) & (modelo_categorias_final == cat) & (modelo_leyes_final > 0)
vals = modelo_leyes_final[mask]
cnt = vals.size
key = f'UG {ug} - {cat}'
if cnt > 0:
vals = vals.astype(float)
results[key] = {
'Número': int(cnt),
'Máximo': float(np.nanmax(vals)),
'Mínimo': float(np.nanmin(vals)),
'Media': float(np.nanmean(vals)),
'Varianza': float(np.nanvar(vals))
}
else:
results[key] = {'Número': 0, 'Máximo': np.nan, 'Mínimo': np.nan, 'Media': np.nan, 'Varianza': np.nan}
# Montar DataFrame y mostrar
df_stats_cat = pd.DataFrame(results).T[['Número', 'Máximo', 'Mínimo', 'Media', 'Varianza']]
display(df_stats_cat.round(2))
| Número | Máximo | Mínimo | Media | Varianza | |
|---|---|---|---|---|---|
| UG 2 - Inferido | 1430.0 | 0.43 | 0.00 | 0.04 | 0.00 |
| UG 2 - Indicado | 8147.0 | 0.53 | 0.00 | 0.05 | 0.00 |
| UG 2 - Medido | 63621.0 | 0.70 | 0.00 | 0.06 | 0.00 |
| UG 3 - Inferido | 295.0 | 0.43 | 0.03 | 0.08 | 0.00 |
| UG 3 - Indicado | 1024.0 | 0.44 | 0.01 | 0.10 | 0.00 |
| UG 3 - Medido | 12169.0 | 0.74 | 0.01 | 0.13 | 0.01 |
| UG 4 - Inferido | 2195.0 | 0.55 | 0.03 | 0.20 | 0.01 |
| UG 4 - Indicado | 19573.0 | 0.68 | 0.03 | 0.22 | 0.01 |
| UG 4 - Medido | 102799.0 | 2.48 | 0.01 | 0.24 | 0.01 |
[ ]:
# Entregar para cada UG y cada categorías global = med+ind+inf y demostrado = med + ind las estadísticas (Número, Max, Min, Mean, Variance) de modelo_leyes_final
from IPython.display import display
ugs = [2, 3, 4]
categories = [['Inferido', 'Indicado', 'Medido'], ['Indicado', 'Medido']]
category_labels = ['Global', 'Demostrado']
results = {}
for ug in ugs:
for cat, label in zip(categories, category_labels):
mask = (modelo_ug == ug) & np.isin(modelo_categorias_final, cat) & (modelo_leyes_final > 0) & np.isfinite(modelo_leyes_final)
vals = modelo_leyes_final[mask]
cnt = vals.size
key = f'UG {ug} - {label}'
if cnt > 0:
vals = vals.astype(float)
results[key] = {
'Número': int(cnt),
'Máximo': float(np.nanmax(vals)),
'Mínimo': float(np.nanmin(vals)),
'Media': float(np.nanmean(vals)),
'Varianza': float(np.nanvar(vals))
}
else:
results[key] = {'Número': 0, 'Máximo': np.nan, 'Mínimo': np.nan, 'Media': np.nan, 'Varianza': np.nan}
# Montar DataFrame y mostrar
df_stats_cat = pd.DataFrame(results).T[['Número', 'Máximo', 'Mínimo', 'Media', 'Varianza']]
display(df_stats_cat.round(2))
| Número | Máximo | Mínimo | Media | Varianza | |
|---|---|---|---|---|---|
| UG 2 - Global | 73198.0 | 0.70 | 0.00 | 0.06 | 0.00 |
| UG 2 - Demostrado | 71768.0 | 0.70 | 0.00 | 0.06 | 0.00 |
| UG 3 - Global | 13488.0 | 0.74 | 0.01 | 0.13 | 0.01 |
| UG 3 - Demostrado | 13193.0 | 0.74 | 0.01 | 0.13 | 0.01 |
| UG 4 - Global | 124567.0 | 2.48 | 0.01 | 0.23 | 0.01 |
| UG 4 - Demostrado | 122372.0 | 2.48 | 0.01 | 0.23 | 0.01 |
[94]:
## Curva Tonelaje (izq.) y ley promedio (der.) in secondary axis para el modelo final de leyes overlayed in the same plot
import plotly.graph_objects as go
# Prepare data
cutoff_values = np.arange(0, df['cu_pct'].quantile(0.96), 0.01)
mtonnage = []
average_grade = []
for cutoff in cutoff_values:
mask = (modelo_leyes_final >= cutoff) & np.isfinite(modelo_leyes_final)
## puede crear otra mascara mask para filtrar por categoría o UG si lo desea
mask = np.logical_and(mask, np.logical_or(modelo_categorias_final == 'Medido', modelo_categorias_final == 'Indicado', modelo_categorias_final == 'Inferido')) # ejemplo: solo categoría Medido
## mask = np.logical_and(mask, modelo_ug == 4) # ejemplo: solo UG 4. COMBINAR AMBOS FILTROS SI ES NECESARIO
total_tonnage = np.sum(mask) * (xsiz * ysiz * zsiz * 2.5) # assuming 2.5 t/m³ (2.5 g/cm³)
total_mtonnage = total_tonnage / 1e6 # convert to million tonnes
mtonnage.append(round(total_mtonnage, 2))
if total_tonnage > 0:
avg_grade = np.nanmean(modelo_leyes_final[mask])
else:
avg_grade = 0
average_grade.append(round(avg_grade, 2))
# Create figure with secondary y-axis
fig = go.Figure()
# Tonnage trace
fig.add_trace(
go.Scatter(
x=cutoff_values,
y=mtonnage,
name='Tonelaje',
line=dict(color='blue'),
yaxis='y1'
)
)
# Average grade trace
fig.add_trace(
go.Scatter(
x=cutoff_values,
y=average_grade,
name='Ley Promedio (%)',
line=dict(color='red'),
yaxis='y2'
)
)
# Update layout for dual y-axes
fig.update_layout(
title='Curva Tonelaje y Ley Promedio vs Corte de Ley (Modelo Final de Leyes)',
xaxis=dict(title='Corte de Ley (%)',gridcolor='rgba(0,0,0,0.2)'),
yaxis=dict(
title='Tonelaje (MTon)',
tickfont=dict(color='blue',
),
side='left',
gridcolor='rgba(0,0,0,0.2)'
),
yaxis2=dict(
title='Ley Promedio (%)',
tickfont=dict(color='red'),
tickformat=',.f',
overlaying='y',
side='right'
),
legend=dict(x=0.1, y=0.9),
font_family="Times New Roman",
font_size=12,
font_color="black",
plot_bgcolor='white',
width=800,
height=500
)
fig.show()